package net.kennux.cubicworld.util;
import java.text.DecimalFormat;
import java.util.LinkedList;
import net.kennux.cubicworld.CubicWorld;
import net.kennux.cubicworld.CubicWorldGame;
import net.kennux.cubicworld.networking.CubicWorldClient;
import net.kennux.cubicworld.voxel.ChunkKey;
import net.kennux.cubicworld.voxel.VoxelWorld;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.PerspectiveCamera;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.graphics.profiling.GLProfiler;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
/**
* Renders debug information.
* The renderDebugInformation() function will render a minecraft like debug
* screen.
*
* @author KennuX
*
*/
public class DebugHelper
{
/**
* Renders debug information in 3d-space.
*
* @param camera
* @param client
*/
public static void renderDebug(PerspectiveCamera camera, CubicWorldGame cubicWorld)
{
if (shapeRenderer == null)
{
shapeRenderer = new ShapeRenderer();
}
if (renderChunkBoundingBoxes)
{
shapeRenderer.begin(ShapeType.Line);
shapeRenderer.setProjectionMatrix(camera.combined);
shapeRenderer.setColor(Color.RED);
// Get all currently loaded chunk keys.
ChunkKey[] keys = cubicWorld.voxelWorld.getKeys();
for (ChunkKey key : keys)
{
Vector3 position = new Vector3(key.x * VoxelWorld.chunkWidth, key.y * VoxelWorld.chunkHeight, key.z * VoxelWorld.chunkDepth);
shapeRenderer.box(position.x, position.y, position.z, VoxelWorld.chunkWidth, VoxelWorld.chunkHeight, -VoxelWorld.chunkDepth);
}
shapeRenderer.end();
}
}
/**
* Renders a minecraft like debug screen.
*
* @param camera
*/
public static void renderDebugInformation(PerspectiveCamera camera, CubicWorldClient client)
{
// Init
if (spriteBatch == null)
{
spriteBatch = new SpriteBatch();
font = new BitmapFont();
fpsCounterStack = new LinkedList<Integer>();
drawcallCounterStack = new LinkedList<Integer>();
upstreamBytesStack = new LinkedList<Integer>();
downstreamBytesStack = new LinkedList<Integer>();
}
// Update information stacks every 20th frame
if (Gdx.graphics.getFrameId() % 2 == 0)
{
// Fps counter
fpsCounterStack.addLast(new Integer(Gdx.graphics.getFramesPerSecond()));
// More than 120 in there?
if (fpsCounterStack.size() > 120)
{
fpsCounterStack.removeFirst();
}
}
// Networking update
upstreamBytesStack.addLast(client.getBytesUpstream());
// More than 120 in there?
if (upstreamBytesStack.size() > 120)
{
upstreamBytesStack.removeFirst();
}
downstreamBytesStack.addLast(client.getBytesDownstream());
// More than 120 in there?
if (downstreamBytesStack.size() > 120)
{
downstreamBytesStack.removeFirst();
}
// Drawcall counter update
drawcallCounterStack.addLast(new Integer(GLProfiler.drawCalls));
// More than 120 in there?
if (drawcallCounterStack.size() > 120)
{
drawcallCounterStack.removeFirst();
}
int screenHeight = Gdx.graphics.getHeight();
int screenWidth = Gdx.graphics.getWidth();
// Render information
spriteBatch.begin();
font.draw(spriteBatch, "Position (XYZ): " + camera.position.x + "|" + camera.position.y + "|" + camera.position.z, 10, screenHeight);
font.draw(spriteBatch, "LookDir (XYZ): " + camera.direction.x + "|" + camera.direction.y + "|" + camera.direction.z, 10, screenHeight - 20);
font.draw(spriteBatch, "FPS: " + Gdx.graphics.getFramesPerSecond(), screenWidth - 100, screenHeight - 20);
font.draw(spriteBatch, "Draw calls: " + GLProfiler.drawCalls, 10, screenHeight - 40);
// TODO Why does this produce the same as Draw calls: ?
font.draw(spriteBatch, "Vertex Count: " + (int) GLProfiler.vertexCount.total, 10, screenHeight - 60);
font.draw(spriteBatch, "Texture Bindings: " + GLProfiler.textureBindings, 10, screenHeight - 80);
// VM Info
int mb = 1024 * 1024;
// Getting the runtime reference from system
Runtime runtime = Runtime.getRuntime();
font.draw(spriteBatch, "Used Memory: " + (runtime.totalMemory() - runtime.freeMemory()) / mb + " MB", 10, screenHeight - 120);
font.draw(spriteBatch, "Free Memory: " + runtime.freeMemory() / mb + " MB", 10, screenHeight - 140);
font.draw(spriteBatch, "Total Memory: " + runtime.totalMemory() / mb + " MB", 10, screenHeight - 160);
font.draw(spriteBatch, "Max Memory: " + runtime.maxMemory() / mb + " MB", 10, screenHeight - 180);
font.draw(spriteBatch, "Java VM: " + System.getProperty("java.vm.name"), 10, screenHeight - 200);
spriteBatch.end();
// Diagrams
if (drawDiagrams)
{
// FPS-Diagram
float[] fpsEntries = new float[fpsCounterStack.size()];
for (int i = 0; i < fpsEntries.length; i++)
fpsEntries[i] = fpsCounterStack.get(i);
StatisticsHelper.renderLineDiagram("FPS", new Vector2(screenWidth - 350, 10), new Vector2(300, 200), fpsEntries, 0f);
// Drawcall-Diagram
float[] drawcallEntries = new float[drawcallCounterStack.size()];
for (int i = 0; i < drawcallEntries.length; i++)
drawcallEntries[i] = drawcallCounterStack.get(i);
StatisticsHelper.renderLineDiagram("Drawcalls", new Vector2(screenWidth - 350, 250), new Vector2(300, 200), drawcallEntries, 0f);
// Render profiling results
String[] profilingNames = new String[] { "Update", "Render" };
Color[] profilingColors = new Color[] { Color.RED, Color.GREEN };
// Get profiling results
float[] profilingValues = new float[profilingNames.length];
for (int i = 0; i < profilingValues.length; i++)
{
// Get profiling result
profilingValues[i] = CubicWorld.getClient().profiler.getProfilerResult(profilingNames[i]);
}
// Render profiling legend
int yAxisPosition = 700;
spriteBatch.begin();
for (int i = 0; i < profilingNames.length; i++)
{
Vector2 currentPosition = new Vector2(screenWidth - 130, yAxisPosition);
font.setColor(profilingColors[i]);
font.draw(spriteBatch, profilingNames[i] + " " + new DecimalFormat("#.##").format((profilingValues[i] / 1000.0f) / 1000.0f), currentPosition.x, currentPosition.y);
yAxisPosition -= 20;
}
spriteBatch.end();
// Reset font color
font.setColor(Color.WHITE);
StatisticsHelper.renderPiechart("Profilings", new Vector2(screenWidth - 350, 500), 100, profilingValues, profilingColors);
}
// Network diagrams
if (drawNetworkDiagrams)
{
// Upstream-Diagram
float[] upstreamEntries = new float[upstreamBytesStack.size()];
for (int i = 0; i < upstreamEntries.length; i++)
upstreamEntries[i] = upstreamBytesStack.get(i).floatValue() / 1024f;
StatisticsHelper.renderLineDiagram("Upstream (kB)", new Vector2(screenWidth - 700, 250), new Vector2(300, 200), upstreamEntries, 0f);
// Downstream-Diagram
float[] downstreamEntries = new float[downstreamBytesStack.size()];
for (int i = 0; i < downstreamEntries.length; i++)
downstreamEntries[i] = downstreamBytesStack.get(i).floatValue() / 1024f;
StatisticsHelper.renderLineDiagram("Downstream (kB)", new Vector2(screenWidth - 700, 10), new Vector2(300, 200), downstreamEntries, 0f);
}
}
/**
* The sprite batch used for rendering the debug information to the screen.
*/
private static SpriteBatch spriteBatch;
/**
* The font used for rendering.
*/
private static BitmapFont font;
/**
* The lifo stack used for recording the 60 latest fps counts.
*/
private static LinkedList<Integer> fpsCounterStack;
/**
* The lifo stack used for recording the 60 latest drawcall counts.
*/
private static LinkedList<Integer> drawcallCounterStack;
/**
* The lifo stack used for recording the 60 latest drawcall counts.
*/
private static LinkedList<Integer> upstreamBytesStack;
/**
* The lifo stack used for recording the 60 latest drawcall counts.
*/
private static LinkedList<Integer> downstreamBytesStack;
// renderDebug() variables
/**
* If this is set to true, diagrams will get drawn.
*/
public static boolean drawDiagrams = false;
/**
* If this is set to true, network diagrams will get drawn.
*/
public static boolean drawNetworkDiagrams = false;
private static ShapeRenderer shapeRenderer;
/**
* If this is set to true renderDebug() will render chunk bounding boxes.
*/
public static boolean renderChunkBoundingBoxes = false;
}